-module(cell).
-export([init_cell/3]).
-export([init_cell/1]).
-include("cell_records.hrl").

%% init_cell(X, Y, Neighbors)
%% starts cell
%% X = Y = integer()
%% X and Y determines position of the cell
%% Neighbors = #nbors
%% list of cell neighbors, start with upper and goes anti-clockwise
init_cell(X, Y, Neighbors) ->
  Cell = #cell{
                state = pause,
                position = {X, Y},
                nbors = Neighbors
              },
  spawn(?MODULE, init_cell, [Cell]).

init_cell(Cell) ->
  say_hello(Cell#cell.nbors),
  Gpid = spawn(growth, init, [(Cell#cell.nbors)#nbors.count, 200, self()]),
  cell(Cell#cell{gpid = Gpid}).

%% cell function, core of the process
cell(Cell) ->
  receive
    Msg -> read_message(Cell, Msg)
  end.


%% ----------------- vegetation connected messages ----------------- %
read_message(Cell, check_veg_level) ->
  Cell#cell.gpid ! {veg_status, Cell#cell.vegetation},
  send_to_nbors(Cell#cell.nbors, request_veg_level, self()),
  cell(Cell);
read_message(Cell, {request_veg_level, Neighbor, Pid}) ->
  Pid ! {reply_veg_level, Neighbor, Cell#cell.vegetation},
  cell(Cell);
read_message(Cell, {reply_veg_level, _Neighbor, Value}) ->
  Cell#cell.gpid ! {reply_veg_level, Value},
  cell(Cell);
read_message(#cell{vegetation = CVeg, position = Pos} = Cell, {boost_veg, Veg}) ->
  %NB = (Cell#cell.nbors)#nbors.count,
  connector ! {veg_state, Pos, CVeg + Veg},
  cell(Cell#cell{vegetation = CVeg + Veg});
read_message(#cell{vegetation = Veg, occupant = Oc} = Cell, {eat_veg, ER}) ->
  if
    Veg > ER ->
      Oc ! {here_you_go, ER},
      cell(Cell#cell{vegetation = Veg - ER});
    true ->
      Oc ! not_enough,
      cell(Cell)
  end;
% ----------------- initiation messages --------------------------- %
read_message(Cell, {hello, Neighbor, Pid}) ->
  Cell#cell.gpid ! new_neighbor,
  cell(remember_neighbor(Cell, Neighbor, Pid));
read_message(Cell, {set, Field, Value}) ->
  cell(set(Cell, Field, Value));
% ----------------- debug messages ---------------------------------%
read_message(Cell, cieple_kolanko) ->
  send_to_nbors(Cell#cell.nbors, print, Cell#cell.position),
  cell(Cell);
read_message(#cell{position = {CX, CY}, vegetation = Veg} = Cell, print_veg) ->
  io:format("~p: (~p, ~p) veg = ~p~n", [self(), CX, CY, Veg]),
  cell(Cell);
read_message(#cell{position = {CX, CY}} = Cell, {print, Neighbor, {X, Y}}) ->
  io:format("(~B, ~B) widzi (~B, ~B) ~w.~n", [CX, CY, X, Y, Neighbor]),
  cell(Cell);
read_message(Cell, print_all) ->
  io:format("~p~n", [Cell]),
  cell(Cell);
% ---------------- control messages --------------------------------%
read_message(#cell{occupant = Oc} = Cell, {occupy, Pid}) ->
  if
    Oc == none ->
      Pid ! {occup_ok, self()},
      cell(Cell#cell{occupant = Pid});
    true ->
      Pid ! {error, already_taken, self()},
      cell(Cell)
  end;
read_message(#cell{position = Pos} = Cell, {get_position, Pid}) ->
  Pid ! {position, Pos},
  cell(Cell);
read_message(#cell{nbors = NB} = Cell, {get_nbors, Pid}) ->
  #nbors{count = Count} = NB,
  Pid ! {nb_count, Count},
  multicast(Cell, all_without_me, 1, {veg_occupation_state, Pid});
read_message(#cell{vegetation = Veg, occupant = Oc} = Cell, {veg_occupation_state, Pid}) ->
  Pid ! {veg_occupation_state, Veg, Oc, self()},
  cell(Cell);
read_message(#cell{position = {CX, CY}} = Cell, {echo, X, Y, Pid}) ->
  if
    ((CX == X) and (CY == Y)) ->
      Pid ! {position, X, Y, self()};
    true ->
      ok
  end,
  cell(Cell);
read_message(Cell, {multicast, Direction, Depth, Msg}) ->
  multicast(Cell, Direction, Depth, Msg);
read_message(Cell, stop) ->
  Cell#cell.gpid ! stop,
  %{CX, CY} = Cell#cell.position,
  %io:format("(~B, ~B) stopped.~n", [CX, CY]),
  ok;
read_message(#cell{gpid = GPid} = Cell, play) ->
  GPid ! play,
  cell(Cell#cell{state = play});
read_message(#cell{gpid = GPid} = Cell, pause) ->
  GPid ! pause,
  cell(Cell#cell{state = pause}).


%% multicast(Cell, Direction, Depth, Msg)
%% Used to propagate message all over the map
%% Depth < 0 means infinite depth
multicast(Cell, all_without_me, Depth, Msg) when Depth /= 0 -> % start
  multicast_to(Cell#cell.nbors, all, Depth - 1, Msg),
  cell(Cell);
multicast(Cell, Dir, Depth, Msg) when Depth /= 0 -> % start
  multicast_to(Cell#cell.nbors, Dir, Depth - 1, Msg),
  read_message(Cell, Msg);
multicast(Cell, _Dir, 0, Msg) ->
  read_message(Cell, Msg).

%% multicast_to(NB, Direction, Depth, Msg) ->
%% multicast() helper
multicast_to(#nbors{upper = U, upper_left = UL, lower_left = LL, lower = L, lower_right = LR, upper_right = UR}, all, Depth, Msg) ->
% when Direction == all, send to all
  send(U, {multicast, lower, Depth, Msg}),
  send(UL, {multicast, lower_right, Depth, Msg}),
  send(LL, {multicast, upper_right, Depth, Msg}),
  send(L, {multicast, upper, Depth, Msg}),
  send(LR, {multicast, upper_left, Depth, Msg}),
  send(UR, {multicast, lower_left, Depth, Msg});
multicast_to(#nbors{lower = L}, upper, Depth, Msg) ->
  send(L, {multicast, upper, Depth, Msg});
multicast_to(#nbors{lower = L, lower_right = LR, upper_right = UR}, upper_left, Depth, Msg) ->
  send(L, {multicast, upper, Depth, Msg}),
  send(LR, {multicast, upper_left, Depth, Msg}),
  send(UR, {multicast, lower_left, Depth, Msg});
multicast_to(#nbors{upper_left = UL, lower_left = LL, lower = L}, upper_right, Depth, Msg) ->
  send(UL, {multicast, lower_right, Depth, Msg}),
  send(LL, {multicast, upper_right, Depth, Msg}),
  send(L, {multicast, upper, Depth, Msg});
multicast_to(#nbors{upper = U, upper_left = UL, upper_right = UR}, lower, Depth, Msg) ->
  send(UR, {multicast, lower_left, Depth, Msg}),
  send(U, {multicast, lower, Depth, Msg}),
  send(UL, {multicast, lower_right, Depth, Msg});
multicast_to(#nbors{upper_right = UR}, lower_left, Depth, Msg) ->
  send(UR, {multicast, lower_left, Depth, Msg});
multicast_to(#nbors{upper_left = UL}, lower_right, Depth, Msg) ->
  send(UL, {multicast, lower_right, Depth, Msg}).

%% set(Cell, Field, Value)
%% Sets Cell's Field to Value
%% Return: #cell
set(Cell, vegetation, Value) ->
  Cell#cell{vegetation = Value};
set(Cell, occupant, Value) ->
  Cell#cell{occupant = Value}.

%% Sends info about oneself to defined in NB neighbors
say_hello(NB) ->
  send_to_nbors(NB, hello, self()).

send_to_nbors(NB, Tag, Msg) ->
  send(NB#nbors.upper, Tag, lower, Msg),
  send(NB#nbors.upper_left, Tag, lower_right, Msg),
  send(NB#nbors.lower_left, Tag, upper_right, Msg),
  send(NB#nbors.lower, Tag, upper, Msg),
  send(NB#nbors.lower_right, Tag, upper_left, Msg),
  send(NB#nbors.upper_right, Tag, lower_left, Msg).

%% send_to_nbors() helper
send(none, _Tag, _NeighborType, _Msg) -> void;
send(Neighbor, Tag, NeighborType, Msg) ->
  Neighbor#neighbor.npid ! {Tag, NeighborType, Msg}.

send(none, _Msg) -> void;
send(To, Msg) ->
  To#neighbor.npid ! Msg.



remember_neighbor(Cell, upper, Pid) ->
  Nbors = Cell#cell.nbors,
  Cell#cell{nbors = Nbors#nbors{count = Nbors#nbors.count + 1, upper = #neighbor{npid = Pid}}};
remember_neighbor(Cell, upper_left, Pid) ->
  Nbors = Cell#cell.nbors,
  Cell#cell{nbors = Nbors#nbors{count = Nbors#nbors.count + 1, upper_left = #neighbor{npid = Pid}}};
remember_neighbor(Cell, lower_left, Pid) ->
  Nbors = Cell#cell.nbors,
  Cell#cell{nbors = Nbors#nbors{count = Nbors#nbors.count + 1, lower_left = #neighbor{npid = Pid}}};
remember_neighbor(Cell, lower, Pid) ->
  Nbors = Cell#cell.nbors,
  Cell#cell{nbors = Nbors#nbors{count = Nbors#nbors.count + 1, lower = #neighbor{npid = Pid}}};
remember_neighbor(Cell, lower_right, Pid) ->
  Nbors = Cell#cell.nbors,
  Cell#cell{nbors = Nbors#nbors{count = Nbors#nbors.count + 1, lower_right = #neighbor{npid = Pid}}};
remember_neighbor(Cell, upper_right, Pid) ->
  Nbors = Cell#cell.nbors,
  Cell#cell{nbors = Nbors#nbors{count = Nbors#nbors.count + 1, upper_right = #neighbor{npid = Pid}}}.
